<?php
/**
 * The scheduler functions.
 * @author Ian Dunn <scholar42@gmail.com>
 * @version 1.0
 * @since 2013-11-02
 */

/*
  The scheduling class is never instantiated.
  Therefore, it is a Singleton class.
*/
require_once 'database.php';
require_once 'order.php';
require_once 'table.php';
require_once 'phase.php';
require_once 'views.php';

class schedule {

    // The constant that determines the weight of priority.
	const constant = 0.5;

	/**
	 * Returns the directory under which the schedule data is stored.
	 * This is read from the configuration file.
	 */
	static function sched_dir() {
		$config = parse_ini_file ("config.ini");
		$sched_dir = $config['sched_dir'];
		return $sched_dir;
	}

	/**
	 * The comparator function for the schedule.
	 *
	 * @param m		One phase object to compare.
	 * @param n		Another phase object to compare.
	 * @return		-1, 0, or 1 depending on the deadlines of the two phases.
	 */
	static function cmp ($m, $n) {
		if ($m->weighted == $n->weighted) {
			return 0;
		}
		return ($m->weighted < $n->weighted) ? -1 : 1;
	}

	/**
	 * The main scheduler function
	 *
	 * @param phases	All of the phases to sort.
	 * @param db		A database object with an open connection.
	 * @param orders	All of the orders from which to pull the
	 *						 deadline information.
	 * @return			An associative array that maps machine id to
	 *						 the sorted list of phases for that machine.
	 */
	static function do_schedule ($phases, &$orders) {
		// Collect the phases to their orders.

		/*
		  Sort orders by deadline.
		  First, weight the deadlines.
		 */
		foreach ($orders as $id => &$order) {
			$dt = new DateTime ($order->deadline);
			$deadline = $dt->getTimeStamp ();

			$order->weighted = (float) $deadline
				* (float) $order->priority
				* schedule::constant;
		}

		uasort ($orders, "schedule::cmp");

		$ords = array();
		foreach ($phases as &$ph) {
			$ords[(int)$ph->ordId][(int)$ph->priority] = $ph;
		}

		// Each element of this should be initialized to zero.
		$mach_times = array();
		$machs = array ();

		$cur_time = (int)(time()/60);

		foreach ($orders as $oid => &$order) {
			$ord_time = 0.0;
			// If an order is already running, set $ord_time = $run_phase->time_req;
			if (!isset ($ords[(int)$oid])) {
				// This shouldn't happen, but just in case...
				continue;
			}
			for ($i = 0; isset($ords[(int)$oid][$i]); $i++) {
				$phase = &$ords[(int)$oid][$i];
				// Handle finished phases.
				if ((int)$phase->status == phase::STATUS_FINISHED) {
					list($start,$stop) = $phase->get_time();
					$phase->est_start_time = (int)($start / 60);
					$phase->est_stop_time = (int)($stop / 60);
					$ord_time = $phase->est_stop_time - $cur_time;
					$machs[$phase->machId][] = $phase;
					continue;
				}

				if (!isset ($mach_times[(int)$phase->machId])) {
					$mach_times[(int)$phase->machId] = 0;
				}

				if ((int)$phase->status == phase::STATUS_RUNNING) {
					list($start,$stop) = $phase->get_time();
					$phase->est_start_time = (int)($start / 60);
					$phase->est_stop_time = $phase->est_start_time + $phase->time_req - $cur_time;
				} else {
					$time = max ($ord_time, $mach_times[(int)$phase->machId]);
					$phase->est_start_time = $time;
					$phase->est_stop_time = $time + $phase->time_req;
				}

				$machs[$phase->machId][] = $phase;
				$mach_times[$phase->machId] = $phase->est_stop_time;
				$ord_time = $phase->est_stop_time;
			}

			$status = (int)$ords[(int)$oid][0]->status;
			$order->start_time = $ords[(int)$oid][0]->est_start_time;
			if (!($status == phase::STATUS_FINISHED || $status == phase::STATUS_RUNNING))
				$order->start_time += $cur_time;

			$order->stop_time = $ord_time + $cur_time;
		}

		// Also need to calculate start and stop times for the orders.
		foreach ($machs as $mid => &$phases) {
			foreach ($phases as &$phase) {
				if ($phase->status != phase::STATUS_RUNNING && $phase->status != phase::STATUS_FINISHED)
					$phase->est_start_time += $cur_time;
				if ($phase->status != phase::STATUS_FINISHED)
					$phase->est_stop_time += $cur_time;
			}
		}

		return $machs;
	}


	/**
	 * Print the schedule to the machine HTML files.
	 *
	 * @param sched		The array returned from do_schedule.
	 * @param orders	The orders with deadlines and priority.
	 * @return			None.
	 */
	static function print_schedule ($sched, $orders) {
		$priorities = array ("","Highest","Higher","Normal","Lower","Lowest");
		foreach ($sched as $mid => $phases) {
			$file_name = sprintf ("%s/machine_%d.php", schedule::sched_dir(), $mid);
			$mach_file = fopen ($file_name, "w");
			$export_data = array ();
			foreach ($phases as $phase) {
				$pid = sprintf ("%d-%d", (int)$phase->ordId, (int)$phase->priority);
				$order = $orders[(int)$phase->ordId];
				$deadline = $order->deadline;
				$priority = $priorities[(int)$order->priority];
				$export_string = "<tr>\n";
				$export_string .= "<td>$pid</td>\n";
				$export_string .= "<td>$deadline</td>\n";
				$export_string .= "<td>$priority</td>\n";
				$export_string .= "</tr>\n";
				$export_data[] = $export_string;
			}
			$export_final = var_export ($export_data, TRUE);
			fprintf ($mach_file, '<?php' . "\n");
			fprintf ($mach_file, '$GLOBALS["mach_vars"] = ' . "%s;\n", $export_final);
			fprintf ($mach_file, '?>' . "\n");
			fclose ($mach_file);
		}
	}

	/**
	 * Collect an array of orders.
	 *
	 * @param db	A database object with an open connection to the database.
	 * @return		An array of all of the order objects in the database.
	 */
	static function collect_orders ($db = NULL) {
		if (!$db)
			$db = new database ();

		$cols = array ("id");
		$ret = array ();
		$result = $db->select_entry ('Orders', $cols);
		for ($i = 0; $i < $result->num_rows; $i++) {
			$arr = $result->fetch_row ();
			$id = (int)$arr[0];
			$order = new order ((int)$id);
			$order->init ($db);
			$ret[$id] = $order;
		}
		$result->free ();
		return $ret;
	}

	/**
	 * Collect an array of phases.
	 *
	 * @param db	A database object with an open connection to the database.
	 * @return		An array of all of the phase objects in the database.
	 */
	static function collect_phases ($db = NULL) {
		if (!$db)
			$db = new database ();

		$ret = array ();
		$cols = array ("id");
		$result = $db->select_entry ('Phases', $cols);
		for ($i = 0; $i < $result->num_rows; $i++) {
			$arr = $result->fetch_row ();
			$id = (int)$arr[0];
			$phase = new phase ((int)$id);
			$phase->init ($db);
			$ret[$id] = $phase;
		}
		$result->free ();
		return $ret;
	}

	// This is really the main entry point.
	static function do_sched ($db = NULL) {
		if (!$db)
			$db = new database ();

		$phases = schedule::collect_phases ($db);
		$orders = schedule::collect_orders ($db);
		$sched = schedule::do_schedule ($phases, $orders);
		// Clear out schedule::sched_dir().
		$handle = opendir (schedule::sched_dir());
		while ($file_name = readdir ($handle)) {
			if (!strcmp ($file_name, ".") || !strcmp ($file_name, ".."))
				continue;
			unlink (schedule::sched_dir() . "/" . $file_name);
		}
		schedule::print_schedule ($sched,$orders);
		views::print_month_view ($orders);
		views::print_weekly_view ($sched);
		views::print_daily_view ($sched);
	}
}

?>
